<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_12</span></h2>

  <p>t;Sleepy", "Prefers dark, quiet places" },</p>

  <p>{ "Grumpy", "Needs to work on attitude" },</p>

  <p>{ "Doc", "Fantasizes about advanced degree"},</p>

  <p>{ "Dopey", "'A' for effort" },</p>

  <p>{ "Sneezy", "Struggles with allergies" },</p>

  <p>{ "Bashful", "Needs self-esteem workshop"},</p>

  <p>};</p>

  <p>public final static String[][] testData2 = {</p>

  <p>{ "Belligerent", "Disruptive influence" },</p>

  <p>{ "Lazy", "Motivational problems" },</p>

  <p>{ "Comatose", "Excellent behavior" }</p>

  <p>};</p>

  <p>public static Map fill(Map m, Object[][] o) {</p>

  <p>for(int i = 0; i &lt; o.length; i++)</p>

  <p>m.put(o[i][0], o[i][1]);</p>

  <p>return m;</p>

  <p>}</p>

  <p>// Producing a Set of the keys:</p>

  <p>public static void printKeys(Map m) {</p>

  <p>System.out.print("Size = " + m.size() +", ");</p>

  <p>System.out.print("Keys: ");</p>

  <p>Collection1.print(m.keySet());</p>

  <p>}</p>

  <p>// Producing a Collection of the values:</p>

  <p>public static void printValues(Map m) {</p>

  <p>System.out.print("Values: ");</p>

  <p>Collection1.print(m.values());</p>

  <p>}</p>

  <p>// Iterating through Map.Entry objects (pairs):</p>

  <p>public static void print(Map m) {</p>

  <p>Collection entries = m.entries();</p>

  <p>Iterator it = entries.iterator();</p>

  <p>while(it.hasNext()) {</p>

  <p>Map.Entry e = (Map.Entry)it.next();</p>

  <p>System.out.println("Key = " + e.getKey() +</p>

  <p>", Value = " + e.getValue());</p>

  <p>}</p>

  <p>}</p>

  <p>public static void test(Map m) {</p>

  <p>fill(m, testData1);</p>

  <p>// Map has 'Set' behavior for keys:</p>

  <p>fill(m, testData1);</p>

  <p>printKeys(m);</p>

  <p>printValues(m);</p>

  <p>print(m);</p>

  <p>String key = testData1[4][0];</p>

  <p>String value = testData1[4][1];</p>

  <p>System.out.println("m.containsKey(\"" + key +</p>

  <p>"\"): " + m.containsKey(key));</p>

  <p>System.out.println("m.get(\"" + key + "\"): "</p>

  <p>+ m.get(key));</p>

  <p>System.out.println("m.containsValue(\""</p>

  <p>+ value + "\"): " +</p>

  <p>m.containsValue(value));</p>

  <p>Map m2 = fill(new TreeMap(), testData2);</p>

  <p>m.putAll(m2);</p>

  <p>printKeys(m);</p>

  <p>m.remove(testData2[0][0]);</p>

  <p>printKeys(m);</p>

  <p>m.clear();</p>

  <p>System.out.println("m.isEmpty(): "</p>

  <p>+ m.isEmpty());</p>

  <p>fill(m, testData1);</p>

  <p>// Operations on the Set change the Map:</p>

  <p>m.keySet().removeAll(m.keySet());</p>

  <p>System.out.println("m.isEmpty(): "</p>

  <p>+ m.isEmpty());</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>System.out.println("Testing HashMap");</p>

  <p>test(new HashMap());</p>

  <p>System.out.println("Testing TreeMap");</p>

  <p>test(new TreeMap());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>printKeys()，printValues()以及print()方法并不只是有用的工具，它们也清楚地揭示了一个Map的Collection“景象”的产生过程。keySet()方法会产生一个Set，它由Map中的键后推得来。在这儿，它只被当作一个Collection对待。values()也得到了类似的对待，它的作用是产生一个List，其中包含了Map中的所有值（注意键必须是独一无二的，而值可以有重复）。由于这些Collection是由Map后推得到的，所以一个Collection中的任何改变都会在相应的Map中反映出来。</p>

  <p>print()方法的作用是收集由entries产生的Iterator（反复器），并用它同时打印出每个“键－值”对的键和值。程序剩余的部分提供了每种Map操作的简单示例，并对每种类型的Map进行了测试。</p>

  <p>当创建自己的类，将其作为Map中的一个键使用时，必须注意到和以前的Set相同的问题。</p>

  <p>8.7.5 决定实施方案</p>

  <p>从早些时候的那幅示意图可以看出，实际上只有三个集合组件：Map，List和Set。而且每个接口只有两种或三种实施方案。若需使用由一个特定的接口提供的功能，如何才能决定到底采取哪一种方案呢？</p>

  <p>为理解这个问题，必须认识到每种不同的实施方案都有自己的特点、优点和缺点。比如在那张示意图中，可以看到Hashtable，Vector和Stack的“特点”是它们都属于“传统”类，所以不会干扰原有的代码。但在另一方面，应尽量避免为新的（Java 1.2）代码使用它们。</p>

  <p>其他集合间的差异通常都可归纳为它们具体是由什么“后推”的。换言之，取决于物理意义上用于实施目标接口的数据结构是什么。例如，ArrayList，LinkedList以及Vector（大致等价于ArrayList）都实现了List接口，所以无论选用哪一个，我们的程序都会得到类似的结果。然而，ArrayList（以及Vector）是由一个数组后推得到的；而LinkedList是根据常规的双重链接列表方式实现的，因为每个单独的对象都包含了数据以及指向列表内前后元素的句柄。正是由于这个原因，假如想在一个列表中部进行大量插入和删除操作，那么LinkedList无疑是最恰当的选择（LinkedList还有一些额外的功能，建立于AbstractSequentialList中）。若非如此，就情愿选择ArrayList，它的速度可能要快一些。</p>

  <p>作为另一个例子，Set既可作为一个ArraySet实现，亦可作为HashSet实现。ArraySet是由一个ArrayList后推得到的，设计成只支持少量元素，特别适合要求创建和删除大量Set对象的场合使用。然而，一旦需要在自己的Set中容纳大量元素，ArraySet的性能就会大打折扣。写一个需要Set的程序时，应默认选择HashSet。而且只有在某些特殊情况下（对性能的提升有迫切的需求），才应切换到ArraySet。</p>

  <p>1. 决定使用何种List</p>

  <p>为体会各种List实施方案间的差异，最简便的方法就是进行一次性能测验。下述代码的作用是建立一个内部基础类，将其作为一个测试床使用。然后为每次测验都创建一个匿名内部类。每个这样的内部类都由一个test()方法调用。利用这种方法，可以方便添加和删除测试项目。</p>

  <p>//: ListPerformance.java</p>

  <p>// Demonstrates performance differences in Lists</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class ListPerformance {</p>

  <p>private static final int REPS = 100;</p>

  <p>private abstract static class Tester {</p>

  <p>String name;</p>

  <p>int size; // Test quantity</p>

  <p>Tester(String name, int size) {</p>

  <p>this.name = name;</p>

  <p>this.size = size;</p>

  <p>}</p>

  <p>abstract void test(List a);</p>

  <p>}</p>

  <p>private static Tester[] tests = {</p>

  <p>new Tester("get", 300) {</p>

  <p>void test(List a) {</p>

  <p>for(int i = 0; i &lt; REPS; i++) {</p>

  <p>for(int j = 0; j &lt; a.size(); j++)</p>

  <p>a.get(j);</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("iteration", 300) {</p>

  <p>void test(List a) {</p>

  <p>for(int i = 0; i &lt; REPS; i++) {</p>

  <p>Iterator it = a.iterator();</p>

  <p>while(it.hasNext())</p>

  <p>it.next();</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("insert", 1000) {</p>

  <p>void test(List a) {</p>

  <p>int half = a.size()/2;</p>

  <p>String s = "test";</p>

  <p>ListIterator it = a.listIterator(half);</p>

  <p>for(int i = 0; i &lt; size * 10; i++)</p>

  <p>it.add(s);</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("remove", 5000) {</p>

  <p>void test(List a) {</p>

  <p>ListIterator it = a.listIterator(3);</p>

  <p>while(it.hasNext()) {</p>

  <p>it.next();</p>

  <p>it.remove();</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>};</p>

  <p>public static void test(List a) {</p>

  <p>// A trick to print out the class name:</p>

  <p>System.out.println("Testing " +</p>

  <p>a.getClass().getName());</p>

  <p>for(int i = 0; i &lt; tests.length; i++) {</p>

  <p>Collection1.fill(a, tests[i].size);</p>

  <p>System.out.print(tests[i].name);</p>

  <p>long t1 = System.currentTimeMillis();</p>

  <p>tests[i].test(a);</p>

  <p>long t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " + (t2 - t1));</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>test(new ArrayList());</p>

  <p>test(new LinkedList());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>内部类Tester是一个抽象类，用于为特定的测试提供一个基础类。它包含了一个要在测试开始时打印的字串、一个用于计算测试次数或元素数量的size参数、用于初始化字段的一个构建器以及一个抽象方法test()。test()做的是最实际的测试工作。各种类型的测试都集中到一个地方：tests数组。我们用继承于Tester的不同匿名内部类来初始化该数组。为添加或删除一个测试项目，只需在数组里简单地添加或移去一个内部类定义即可，其他所有工作都是自动进行的。</p>

  <p>首先用元素填充传递给test()的List，然后对tests数组中的测试计时。由于测试用机器的不同，结果当然也会有所区别。这个程序的宗旨是揭示出不同集合类型的相对性能比较。下面是某一次运行得到的结果：</p>

  <p>类型 获取 反复 插入 删除</p>

  <p>ArrayList 110 270 1920 4780</p>

  <p>LinkedList 1870 7580 170 110</p>

  <p>可以看出，在ArrayList中进行随机访问（即get()）以及循环反复是最划得来的；但对于LinkedList却是一个不小的开销。但另一方面，在列表中部进行插入和删除操作对于LinkedList来说却比ArrayList划算得多。我们最好的做法也许是先选择一个ArrayList作为自己的默认起点。以后若发现由于大量的插入和删除造成了性能的降低，再考虑换成LinkedList不迟。</p>

  <p>2. 决定使用何种Set</p>

  <p>可在ArraySet以及HashSet间作出选择，具体取决于Set的大小（如果需要从一个Set中获得一个顺序列表，请用TreeSet；注释⑧）。下面这个测试程序将有助于大家作出这方面的抉择：</p>

  <p>//: SetPerformance.java</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class SetPerformance {</p>

  <p>private static final int REPS = 200;</p>

  <p>private abstract static class Tester {</p>

  <p>String name;</p>

  <p>Tester(String name) { this.name = name; }</p>

  <p>abstract void test(Set s, int size);</p>

  <p>}</p>

  <p>private static Tester[] tests = {</p>

  <p>new Tester("add") {</p>

  <p>void test(Set s, int size) {</p>

  <p>for(int i = 0; i &lt; REPS; i++) {</p>

  <p>s.clear();</p>

  <p>Collection1.fill(s, size);</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("contains") {</p>

  <p>void test(Set s, int size) {</p>

  <p>for(int i = 0; i &lt; REPS; i++)</p>

  <p>for(int j = 0; j &lt; size; j++)</p>

  <p>s.contains(Integer.toString(j));</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("iteration") {</p>

  <p>void test(Set s, int size) {</p>

  <p>for(int i = 0; i &lt; REPS * 10; i++) {</p>

  <p>Iterator it = s.iterator();</p>

  <p>while(it.hasNext())</p>

  <p>it.next();</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>};</p>

  <p>public static void test(Set s, int size) {</p>

  <p>// A trick to print out the class name:</p>

  <p>System.out.println("Testing " +</p>

  <p>s.getClass().getName() + " size " + size);</p>

  <p>Collection1.fill(s, size);</p>

  <p>for(int i = 0; i &lt; tests.length; i++) {</p>

  <p>System.out.print(tests[i].name);</p>

  <p>long t1 = System.currentTimeMillis();</p>

  <p>tests[i].test(s, size);</p>

  <p>long t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " +</p>

  <p>((double)(t2 - t1)/(double)size));</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>// Small:</p>

  <p>test(new TreeSet(), 10);</p>

  <p>test(new HashSet(), 10);</p>

  <p>// Medium:</p>

  <p>test(new TreeSet(), 100);</p>

  <p>test(new HashSet(), 100);</p>

  <p>// Large:</p>

  <p>test(new HashSet(), 1000);</p>

  <p>test(new TreeSet(), 1000);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>⑧：TreeSet在本书写作时尚未成为一个正式的特性，但在这个例子中可以很轻松地为其添加一个测试。</p>

  <p>最后对ArraySet的测试只有500个元素，而不是1000个，因为它太慢了。</p>

  <p>类型 测试大小 添加 包含 反复</p>

  <p>Type</p>

  <p>Test size</p>

  <p>Add</p>

  <p>Contains</p>

  <p>Iteration 10</p>

  <p>22.0</p>

  <p>11.0</p>

  <p>16.0</p>

  <p>TreeSet</p>

  <p>100</p>

  <p>22.5</p>

  <p>13.2</p>

  <p>12.1 1000</p>

  <p>31.1</p>

  <p>18.7</p>

  <p>11.8 10</p>

  <p>5.0</p>

  <p>6.0</p>

  <p>27.0</p>

  <p>HashSet</p>

  <p>100</p>

  <p>6.6</p>

  <p>6.6</p>

  <p>10.9 1000</p>

  <p>7.4</p>

  <p>6.6</p>

  <p>9.5进行add()以及contains()操作时，HashSet显然要比ArraySet出色得多，而且性能明显与元素的多寡关系不大。一般编写程序的时候，几乎永远用不着使用ArraySet。</p>

  <p>3. 决定使用何种Map</p>

  <p>选择不同的Map实施方案时，注意Map的大小对于性能的影响是最大的，下面这个测试程序清楚地阐示了这一点：</p>

  <p>//: MapPerformance.java</p>

  <p>// Demonstrates performance differences in Maps</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class MapPerformance {</p>

  <p>private static final int REPS = 200;</p>

  <p>public static Map fill(Map m, int size) {</p>

  <p>for(int i = 0; i &lt; size; i++) {</p>

  <p>String x = Integer.toString(i);</p>

  <p>m.put(x, x);</p>

  <p>}</p>

  <p>return m;</p>

  <p>}</p>

  <p>private abstract static class Tester {</p>

  <p>String name;</p>

  <p>Tester(String name) { this.name = name; }</p>

  <p>abstract void test(Map m, int size);</p>

  <p>}</p>

  <p>private static Tester[] tests = {</p>

  <p>new Tester("put") {</p>

  <p>void test(Map m, int size) {</p>

  <p>for(int i = 0; i &lt; REPS; i++) {</p>

  <p>m.clear();</p>

  <p>fill(m, size);</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("get") {</p>

  <p>void test(Map m, int size) {</p>

  <p>for(int i = 0; i &lt; REPS; i++)</p>

  <p>for(int j = 0; j &lt; size; j++)</p>

  <p>m.get(Integer.toString(j));</p>

  <p>}</p>

  <p>},</p>

  <p>new Tester("iteration") {</p>

  <p>void test(Map m, int size) {</p>

  <p>for(int i = 0; i &lt; REPS * 10; i++) {</p>

  <p>Iterator it = m.entries().iterator();</p>

  <p>while(it.hasNext())</p>

  <p>it.next();</p>

  <p>}</p>

  <p>}</p>

  <p>},</p>

  <p>};</p>

  <p>public static void test(Map m, int size) {</p>

  <p>// A trick to print out the class name:</p>

  <p>System.out.println("Testing " +</p>

  <p>m.getClass().getName() + " size " + size);</p>

  <p>fill(m, size);</p>

  <p>for(int i = 0; i &lt; tests.length; i++) {</p>

  <p>System.out.print(tests[i].name);</p>

  <p>long t1 = System.currentTimeMillis();</p>

  <p>tests[i].test(m, size);</p>

  <p>long t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " +</p>

  <p>((double)(t2 - t1)/(double)size));</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>// Small:</p>

  <p>test(new Hashtable(), 10);</p>

  <p>test(new HashMap(), 10);</p>

  <p>test(new TreeMap(), 10);</p>

  <p>// Medium:</p>

  <p>test(new Hashtable(), 100);</p>

  <p>test(new HashMap(), 100);</p>

  <p>test(new TreeMap(), 100);</p>

  <p>// Large:</p>

  <p>test(new HashMap(), 1000);</p>

  <p>test(new Hashtable(), 1000);</p>

  <p>test(new TreeMap(), 1000);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>由于Map的大小是最严重的问题，所以程序的计时测试按Map的大小（或容量）来分割时间，以便得到令人信服的测试结果。下面列出一系列结果（在你的机器上可能不同）：</p>

  <p>类型 测试大小 置入 取出 反复</p>

  <p>Type</p>

  <p>Test size</p>

  <p>Put</p>

  <p>Get</p>

  <p>Iteration</p>

  <p>10</p>

  <p>11.0</p>

  <p>5.0</p>

  <p>44.0</p>

  <p>Hashtable</p>

  <p>100</p>

  <p>7.7</p>

  <p>7.7</p>

  <p>16.5 1000</p>

  <p>8.0</p>

  <p>8.0</p>

  <p>14.4</p>

  <p>10</p>

  <p>16.0</p>

  <p>11.0</p>

  <p>22.0</p>

  <p>TreeMap</p>

  <p>100</p>

  <p>25.8</p>

  <p>15.4</p>

  <p>13.2 1000</p>

  <p>33.8</p>

  <p>20.9</p>

  <p>13.6</p>

  <p>10</p>

  <p>11.0</p>

  <p>6.0</p>

  <p>33.0</p>

  <p>HashMap</p>

  <p>100</p>

  <p>8.2</p>

  <p>7.7</p>

  <p>13.7 1000</p>

  <p>8.0</p>

  <p>7.8</p>

  <p>11.9即使大小为10，ArrayMap的性能也要比HashMap差――除反复循环时以外。而在使用Map时，反复的作用通常并不重要（get()通常是我们时间花得最多的地方）。TreeMap提供了出色的put()以及反复时间，但get()的性能并不佳。但是，我们为什么仍然需要使用TreeMap呢？这样一来，我们可以不把它作为Map使用，而作为创建顺序列表的一种途径。树的本质在于它总是顺序排列的，不必特别进行排序（它的排序方式马上就要讲到）。一旦填充了一个TreeMap，就可以调用keySet()来获得键的一个Set“景象”。然后用toArray()产生包含了那些键的一个数组。随后，可用static方法Array.binarySearch()快速查找排好序的数组中的内容。当然，也许只有在HashMap的行为不可接受的时候，才需要采用这种做法。因为HashMap的设计宗旨就是进行快速的检索操作。最后，当我们使用Map时，首要的选择应该是HashMap。只有在极少数情况下才需要考虑其他方法。</p>

  <p>此外，在上面那张表里，有另一个性能问题没有反映出来。下述程序用于测试不同类型Map的创建速度：</p>

  <p>//: MapCreation.java</p>

  <p>// Demonstrates time differences in Map creation</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class MapCreation {</p>

  <p>public static void main(String[] args) {</p>

  <p>final long REPS = 100000;</p>

  <p>long t1 = System.currentTimeMillis();</p>

  <p>System.out.print("Hashtable");</p>

  <p>for(long i = 0; i &lt; REPS; i++)</p>

  <p>new Hashtable();</p>

  <p>long t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " + (t2 - t1));</p>

  <p>t1 = System.currentTimeMillis();</p>

  <p>System.out.print("TreeMap");</p>

  <p>for(long i = 0; i &lt; REPS; i++)</p>

  <p>new TreeMap();</p>

  <p>t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " + (t2 - t1));</p>

  <p>t1 = System.currentTimeMillis();</p>

  <p>System.out.print("HashMap");</p>

  <p>for(long i = 0; i &lt; REPS; i++)</p>

  <p>new HashMap();</p>

  <p>t2 = System.currentTimeMillis();</p>

  <p>System.out.println(": " + (t2 - t1));</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在写这个程序期间，TreeMap的创建速度比其他两种类型明显快得多（但你应亲自尝试一下，因为据说新版本可能会改善ArrayMap的性能）。考虑到这方面的原因，同时由于前述TreeMap出色的put()性能，所以如果需要创建大量Map，而且只有在以后才需要涉及大量检索操作，那么最佳的策略就是：创建和填充TreeMap；以后检索量增大的时候，再将重要的TreeMap转换成HashMap――使用HashMap(Map)构建器。同样地，只有在事实证明确实存在性能瓶颈后，才应关心这些方面的问题――先用起来，再根据需要加快速度。</p>

  <p>8.7.6 未支持的操作</p>

  <p>利用static（静态）数组Arrays.toList()，也许能将一个数组转换成List，如下所示：</p>

  <p>//: Unsupported.java</p>

  <p>// Sometimes methods defined in the Collection</p>

  <p>// interfaces don't work!</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Unsupported {</p>

  <p>private static String[] s = {</p>

  <p>"one", "two", "three", "four", "five",</p>

  <p>"six", "seven", "eight", "nine", "ten",</p>

  <p>};</p>

  <p>static List a = Arrays.toList(s);</p>

  <p>static List a2 = Arrays.toList(</p>

  <p>new String[] { s[3], s[4], s[5] });</p>

  <p>public static void main(String[] args) {</p>

  <p>Collection1.print(a); // Iteration</p>

  <p>System.out.println(</p>

  <p>"a.contains(" + s[0] + ") = " +</p>

  <p>a.contains(s[0]));</p>

  <p>System.out.println(</p>

  <p>"a.containsAll(a2) = " +</p>

  <p>a.containsAll(a2));</p>

  <p>System.out.println("a.isEmpty() = " +</p>

  <p>a.isEmpty());</p>

  <p>System.out.println(</p>

  <p>"a.indexOf(" + s[5] + ") = " +</p>

  <p>a.indexOf(s[5]));</p>

  <p>// Traverse backwards:</p>

  <p>ListIterator lit = a.listIterator(a.size());</p>

  <p>while(lit.hasPrevious())</p>

  <p>System.out.print(lit.previous());</p>

  <p>System.out.println();</p>

  <p>// Set the elements to different values:</p>

  <p>for(int i = 0; i &lt; a.size(); i++)</p>

  <p>a.set(i, "47");</p>

  <p>Collection1.print(a);</p>

  <p>// Compiles, but won't run:</p>

  <p>lit.add("X"); // Unsupported operation</p>

  <p>a.clear(); // Unsupported</p>

  <p>a.add("eleven"); // Unsupported</p>

  <p>a.addAll(a2); // Unsupported</p>

  <p>a.retainAll(a2); // Unsupported</p>

  <p>a.remove(s[0]); // Unsupported</p>

  <p>a.removeAll(a2); // Unsupported</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>从中可以看出，实际只实现了Collection和List接口的一部分。剩余的方法导致了不受欢迎的一种情况，名为UnsupportedOperationException。在下一章里，我们会讲述违例的详细情况，但在这里有必要进行一下简单说明。这里的关键在于“集合接口”，以及新集合库内的另一些接口，它们都包含了“可选的”方法。在实现那些接口的集合类中，或者提供、或者没有提供对那些方法的支持。若调用一个未获支持的方法，就会导致一个UnsupportedOperationException（操作未支持违例），这表明出现了一个编程错误。</p>

  <p>大家或许会觉得奇怪，不是说“接口”和基础类最大的“卖点”就是它们许诺这些方法能产生一些有意义的行为吗？上述违例破坏了那个许诺――它调用的一部分方法不仅不能产生有意义的行为，而且还会中止程序的运行。在这些情况下，类型的所谓安全保证似乎显得一钱不值！但是，情况并没有想象的那么坏。通过Collection，List，Set或者Map，编译器仍然限制我们只能调用那个接口中的方法，所以它和Smalltalk还是存在一些区别的（在Smalltalk中，可为任何对象调用任何方法，而且只有在运行程序时才知道这些调用是否可行）。除此以外，以Collection作为自变量的大多数方法只能从那个集合中读取数据――Collection的所有“read”方法都不是可选的。</p>

  <p>这样一来，系统就可避免在设计期间出现接口的冲突。而在集合库的其他设计方案中，最终经常都会得到数量过多的接口，用它们描述基本方案的每一种变化形式，所以学习和掌握显得非常困难。有些时候，甚至难于捕捉接口中的所有特殊情况，因为人们可能设计出任何新接口。但Java的“不支持的操作”方法却达到了新集合库的一个重要设计目标：易于学习和使用。但是，为了使这一方法真正有效，却需满足下述条件：</p>

  <p>(1) UnsupportedOperationException必须属于一种“非常”事件。也就是说，对于大多数类来说，所有操作都应是可行的。只有在一些特殊情况下，一、两个操作才可能未获支持。新集合库满足了这一条件，因为绝大多数时候用到的类――ArrayList，LinkedList，HashList和HashMap，以及其他集合方案――都提供了对所有操作的支持。但是，如果想新建一个集合，同时不想为集合接口中的所有方法都提供有意义的定义，同时令其仍与现有库配合，这种设计方法也确实提供了一个“后门”可以利用。</p>

  <p>(2) 若一个操作未获支持，那么UnsupportedOperationException（未支持的操作违例）极有可能在实现期间出现，则不是在产品已交付给客户以后才会出现。它毕竟指出的是一个编程错误――不正确地使用了一个类。这一点不能十分确定，通过也可以看出这种方案的“试验”特征――只有经过多次试验，才能找出最理想的工作方式。</p>

  <p>在上面的例子中，Arrays.toList()产生了一个List（列表），该列表是由一个固定长度的数组后推出来的。因此唯一能够支持的就是那些不改变数组长度的操作。在另一方面，若请求一个新接口表达不同种类的行为（可能叫作“FixedSizeList”――固定长度列表），就有遭遇更大的复杂程度的危险。这样一来，以后试图使用库的时候，很快就会发现自己不知从何处下手。</p>

  <p>对那些采用Collection，List，Set或者Map作为参数的方法，它们的文档应当指出哪些可选的方法是必须实现的。举个例子来说，排序要求实现set()和Iterator.set()方法，但不包括add()和remove()。</p>

  <p>8.7.7 排序和搜索</p>

  <p>Java 1.2添加了自己的一套实用工具，可用来对数组或列表进行排列和搜索。这些工具都属于两个新类的“静态”方法。这两个类分别是用于排序和搜索数组的Arrays，以及用于排序和搜索列表的Collections。</p>

  <p>1. 数组</p>

  <p>Arrays类为所有基本数据类型的数组提供了一个过载的sort()和binarySearch()，它们亦可用于String和Object。下面这个例子显示出如何排序和搜索一个字节数组（其他所有基本数据类型都是类似的）以及一个String数组：</p>

  <p>//: Array1.java</p>

  <p>// Testing the sorting &amp; searching in Arrays</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Array1 {</p>

  <p>static Random r = new Random();</p>

  <p>static String ssource =</p>

  <p>"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +</p>

  <p>"abcdefghijklmnopqrstuvwxyz";</p>

  <p>static char[] src = ssource.toCharArray();</p>

  <p>// Create a random String</p>

  <p>public static String randString(int length) {</p>

  <p>char[] buf = new char[length];</p>

  <p>int rnd;</p>

  <p>for(int i = 0; i &lt; length; i++) {</p>

  <p>rnd = Math.abs(r.nextInt()) % src.length;</p>

  <p>buf[i] = src[rnd];</p>

  <p>}</p>

  <p>return new String(buf);</p>

  <p>}</p>

  <p>// Create a random array of Strings:</p>

  <p>public static</p>

  <p>String[] randStrings(int length, int size) {</p>

  <p>String[] s = new String[size];</p>

  <p>for(int i = 0; i &lt; size; i++)</p>

  <p>s[i] = randString(length);</p>

  <p>return s;</p>

  <p>}</p>

  <p>public static void print(byte[] b) {</p>

  <p>for(int i = 0; i &lt; b.length; i++)</p>

  <p>System.out.print(b[i] + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>public static void print(String[] s) {</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>System.out.print(s[i] + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>byte[] b = new byte[15];</p>

  <p>r.nextBytes(b); // Fill with random bytes</p>

  <p>print(b);</p>

  <p>Arrays.sort(b);</p>

  <p>print(b);</p>

  <p>int loc = Arrays.binarySearch(b, b[10]);</p>

  <p>System.out.println("Location of " + b[10] +</p>

  <p>" = " + loc);</p>

  <p>// Test String sort &amp; search:</p>

  <p>String[] s = randStrings(4, 10);</p>

  <p>print(s);</p>

  <p>Arrays.sort(s);</p>

  <p>print(s);</p>

  <p>loc = Arrays.binarySearch(s, s[4]);</p>

  <p>System.out.println("Location of " + s[4] +</p>

  <p>" = " + loc);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>类的第一部分包含了用于产生随机字串对象的实用工具，可供选择的随机字母保存在一个字符数组中。randString()返回一个任意长度的字串；而readStrings()创建随机字串的一个数组，同时给定每个字串的长度以及希望的数组大小。两个print()方法简化了对示范数组的显示。在main()中，Random.nextBytes()用随机选择的字节填充数组自变量（没有对应的Random方法用于创建其他基本数据类型的数组）。获得一个数组后，便可发现为了执行sort()或者binarySearch()，只需发出一次方法调用即可。与binarySearch()有关的还有一个重要的警告：若在执行一次binarySearch()之前不调用sort()，便会发生不可预测的行为，其中甚至包括无限循环。</p>

  <p>对String的排序以及搜索是相似的，但在运行程序的时候，我们会注意到一个有趣的现象：排序遵守的是字典顺序，亦即大写字母在字符集中位于小写字母的前面。因此，所有大写字母都位于列表的最前面，后面再跟上小写字母――Z居然位于a的前面。似乎连电话簿也是这样排序的。</p>

  <p>2. 可比较与比较器</p>

  <p>但假若我们不满足这一排序方式，又该如何处理呢？例如本书后面的索引，如果必须对以A或a开头的词条分别到两处地方查看，那么肯定会使读者颇不耐烦。</p>

  <p>若想对一个Object数组进行排序，那么必须解决一个问题。根据什么来判定两个Object的顺序呢？不幸的是，最初的Java设计者并不认为这是一个重要的问题，否则就已经在根类Object里定义它了。这样造成的一个后果便是：必须从外部进行Object的排序，而且新的集合库提供了实现这一操作的标准方式（最理想的是在Object里定义它）。</p>

  <p>针对Object数组（以及String，它当然属于Object的一种），可使用一个sort()，并令其接纳另一个参数：实现了Comparator接口（即“比较器”接口，新集合库的一部分）的一个对象，并用它的单个compare()方法进行比较。这个方法将两个准备比较的对象作为自己的参数使用――若第一个参数小于第二个，返回一个负整数；若相等，返回零；若第一个参数大于第二个，则返回正整数。基于这一规则，上述例子的String部分便可重新写过，令其进行真正按字母顺序的排序：</p>

  <p>//: AlphaComp.java</p>

  <p>// Using Comparator to perform an alphabetic sort</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class AlphaComp implements Comparator {</p>

  <p>public int compare(Object o1, Object o2) {</p>

  <p>// Assume it's used only for Strings...</p>

  <p>String s1 = ((String)o1).toLowerCase();</p>

  <p>String s2 = ((String)o2).toLowerCase();</p>

  <p>return s1.compareTo(s2);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>String[] s = Array1.randStrings(4, 10);</p>

  <p>Array1.print(s);</p>

  <p>AlphaComp ac = new AlphaComp();</p>

  <p>Arrays.sort(s, ac);</p>

  <p>Array1.print(s);</p>

  <p>// Must use the Comparator to search, also:</p>

  <p>int loc = Arrays.binarySearch(s, s[3], ac);</p>

  <p>System.out.println("Location of " + s[3] +</p>

  <p>" = " + loc);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>通过造型为String，compare()方法会进行“暗示”性的测试，保证自己操作的只能是String对象――运行期系统会捕获任何差错。将两个字串都强迫换成小写形式后，String.compareTo()方法会产生预期的结果。</p>

  <p>若用自己的Comparator来进行一次sort()，那么在使用binarySearch()时必须使用那个相同的Comparator。</p>

  <p>Arrays类提供了另一个sort()方法，它会采用单个自变量：一个Object数组，但没有Comparator。这个sort()方法也必须用同样的方式来比较两个Object。通过实现Comparable接口，它采用了赋予一个类的“自然比较方法”。这个接口含有单独一个方法――compareTo()，能分别根据它小于、等于或者大于自变量而返回负数、零或者正数，从而实现对象的比较。下面这个例子简单地阐示了这一点：</p>

  <p>//: CompClass.java</p>

  <p>// A class that implements Comparable</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class CompClass implements Comparable {</p>

  <p>private int i;</p>

  <p>public CompClass(int ii) { i = ii; }</p>

  <p>public int compareTo(Object o) {</p>

  <p>// Implicitly tests for correct type:</p>

  <p>int argi = ((CompClass)o).i;</p>

  <p>if(i == argi) return 0;</p>

  <p>if(i &lt; argi) return -1;</p>

  <p>return 1;</p>

  <p>}</p>

  <p>public static void print(Object[] a) {</p>

  <p>for(int i = 0; i &lt; a.length; i++)</p>

  <p>System.out.print(a[i] + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>public String toString() { return i + ""; }</p>

  <p>public static void main(String[] args) {</p>

  <p>CompClass[] a = new CompClass[20];</p>

  <p>for(int i = 0; i &lt; a.length; i++)</p>

  <p>a[i] = new CompClass(</p>

  <p>(int)(Math.random() *100));</p>

  <p>print(a);</p>

  <p>Arrays.sort(a);</p>

  <p>print(a);</p>

  <p>int loc = Arrays.binarySearch(a, a[3]);</p>

  <p>System.out.println("Location of " + a[3] +</p>

  <p>" = " + loc);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>当然，我们的compareTo()方法亦可根据实际情况增大复杂程度。</p>

  <p>3. 列表</p>

  <p>可用与数组相同的形式排序和搜索一个列表（List）。用于排序和搜索列表的静态方法包含在类Collections中，但它们拥有与Arrays中差不多的签名：sort(List)用于对一个实现了Comparable的对象列表进行排序；binarySearch(List,Object)用于查找列表中的某个对象；sort(List,Comparator)利用一个“比较器”对一个列表进行排序；而binarySearch(List,Object,Comparator)则用于查找那个列表中的一个对象（注释⑨）。下面这个例子利用了预先定义好的CompClass和AlphaComp来示范Collections中的各种排序工具：</p>

  <p>//: ListSort.java</p>

  <p>// Sorting and searching Lists with 'Collections'</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class ListSort {</p>

  <p>public static void main(String[] args) {</p>

  <p>final int SZ = 20;</p>

  <p>// Using "natural comparison method":</p>

  <p>List a = new ArrayList();</p>

  <p>for(int i = 0; i &lt; SZ; i++)</p>

  <p>a.add(new CompClass(</p>

  <p>(int)(Math.random() *100)));</p>

  <p>Collection1.print(a);</p>

  <p>Collections.sort(a);</p>

  <p>Collection1.print(a);</p>

  <p>Object find = a.get(SZ/2);</p>

  <p>int loc = Collections.binarySearch(a, find);</p>

  <p>System.out.println("Location of " + find +</p>

  <p>" = " + loc);</p>

  <p>// Using a Comparator:</p>

  <p>List b = new ArrayList();</p>

  <p>for(int i = 0; i &lt; SZ; i++)</p>

  <p>b.add(Array1.randString(4));</p>

  <p>Collection1.print(b);</p>

  <p>AlphaComp ac = new AlphaComp();</p>

  <p>Collections.sort(b, ac);</p>

  <p>Collection1.print(b);</p>

  <p>find = b.get(SZ/2);</p>

  <p>// Must use the Comparator to search, also:</p>

  <p>loc = Collections.binarySearch(b, find, ac);</p>

  <p>System.out.println("Location of " + find +</p>

  <p>" = " + loc);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>⑨：在本书写作时，已宣布了一个新的Collections.stableSort()，可用它进行合并式排序，但还没有它的测试版问世。</p>

  <p>这些方法的用法与在Arrays中的用法是完全一致的，只是用一个列表代替了数组。</p>

  <p>TreeMap也必须根据Comparable或者Comparator对自己的对象进行排序。</p>

  <p>8.7.8 实用工具</p>

  <p>Collections类中含有其他大量有用的实用工具：</p>

  <p>enumeration(Collection)</p>

  <p>Produces an old-style Enumeration for the argument.</p>

  <p>max(Collection)</p>

  <p>min(Collection)</p>

  <p>Produces the maximum or minimum element in the argument using the natural comparison method of the objects in the Collection.</p>

  <p>max(Collection, Comparator)</p>

  <p>min(Collection, Comparator)</p>

  <p>Produces the maximum or minimum element in the Collection using the Comparator.</p>

  <p>nCopies(int n, Object o)</p>

  <p>Returns an immutable List of size n whose handles all point to o.</p>

  <p>subList(List, int min, int max)</p>

  <p>Returns a new List backed by the specified argument List that is a window into that argument with indexes starting at min and stopping just before max.enumeration(Collection) 为自变量产生原始风格的Enumeration（枚举）</p>

  <p>max(Collection)，min(Collection) 在自变量中用集合内对象的自然比较方法产生最大或最小元素</p>

  <p>max(Collection,Comparator)，min(Collection,Comparator) 在集合内用比较器产生最大或最小元素</p>

  <p>nCopies(int n, Object o) 返回长度为n的一个不可变列表，它的所有句柄均指向o</p>

  <p>subList(List,int min,int max) 返回由指定参数列表后推得到的一个新列表。可将这个列表想象成一个“窗口”，它自索引为min的地方开始，正好结束于max的前面</p>

  <p>注意min()和max()都是随同Collection对象工作的，而非随同List，所以不必担心Collection是否需要排序（就象早先指出的那样，在执行一次binarySearch()――即二进制搜索――之前，必须对一个List或者一个数组执行sort()）。</p>

  <p>1. 使Collection或Map不可修改</p>

  <p>通常，创建Collection或Map的一个“只读”版本显得更有利一些。Collections类允许我们达到这个目标，方法是将原始容器传递进入一个方法，并令其传回一个只读版本。这个方法共有四种变化形式，分别用于Collection（如果不想把集合当作一种更特殊的类型对待）、List、Set以及Map。下面这个例子演示了为它们分别构建只读版本的正确方法：</p>

  <p>//: ReadOnly.java</p>

  <p>// Using the Collections.unmodifiable methods</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class ReadOnly {</p>

  <p>public static void main(String[] args) {</p>

  <p>Collection c = new ArrayList();</p>

  <p>Collection1.fill(c); // Insert useful data</p>

  <p>c = Collections.unmodifiableCollection(c);</p>

  <p>Collection1.print(c); // Reading is OK</p>

  <p>//! c.add("one"); // Can't change it</p>

  <p>List a = new ArrayList();</p>

  <p>Collection1.fill(a);</p>

  <p>a = Collections.unmodifiableList(a);</p>

  <p>ListIterator lit = a.listIterator();</p>

  <p>System.out.println(lit.next()); // Reading OK</p>

  <p>//! lit.add("one"); // Can't change it</p>

  <p>Set s = new HashSet();</p>

  <p>Collection1.fill(s);</p>

  <p>s = Collections.unmodifiableSet(s);</p>

  <p>Collection1.print(s); // Reading OK</p>

  <p>//! s.add("one"); // Can't change it</p>

  <p>Map m = new HashMap();</p>

  <p>Map1.fill(m, Map1.testData1);</p>

  <p>m = Collections.unmodifiableMap(m);</p>

  <p>Map1.print(m); // Reading OK</p>

  <p>//! m.put("Ralph", "Howdy!");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>对于每种情况，在将其正式变为只读以前，都必须用有有效的数据填充容器。一旦载入成功，最佳的做法就是用“不可修改”调用产生的句柄替换现有的句柄。这样做可有效避免将其变成不可修改后不慎改变其中的内容。在另一方面，该工具也允许我们在一个类中将能够修改的容器保持为private状态，并可从一个方法调用中返回指向那个容器的一个只读句柄。这样一来，虽然我们可在类里修改它，但其他任何人都只能读。</p>

  <p>为特定类型调用“不可修改”的方法不会造成编译期间的检查，但一旦发生任何变化，对修改特定容器的方法的调用便会产生一个UnsupportedOperationException违例。</p>

  <p>2. Collection或Map的同步</p>

  <p>synchronized关键字是“多线程”机制一个非常重要的部分。我们到第14章才会对这一机制作深入的探讨。在这儿，大家只需注意到Collections类提供了对整个容器进行自动同步的一种途径。它的语法与“不可修改”的方法是类似的：</p>

  <p>//: Synchronization.java</p>

  <p>// Using the Collections.synchronized methods</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Synchronization {</p>

  <p>public static void main(String[] args) {</p>

  <p>Collection c =</p>

  <p>Collections.synchronizedCollection(</p>

  <p>new ArrayList());</p>

  <p>List list = Collections.synchronizedList(</p>

  <p>new ArrayList());</p>

  <p>Set s = Collections.synchronizedSet(</p>

  <p>new HashSet());</p>

  <p>Map m = Collections.synchronizedMap(</p>

  <p>new HashMap());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在这种情况下，我们通过适当的“同步”方法直接传递新容器；这样做可避免不慎暴露出未同步的版本。</p>

  <p>新集合也提供了能防止多个进程同时修改一个容器内容的机制。若在一个容器里反复，同时另一些进程介入，并在那个容器中插入、删除或修改一个对象，便会面临发生冲突的危险。我们可能已传递了那个对象，可能它位位于我们前面，可能容器的大小在我们调用size()后已发生了收缩――我们面临各种各样可能的危险。针对这个问题，新的集合库集成了一套解决的机制，能查出除我们的进程自己需要负责的之外的、对容器的其他任何修改。若探测到有其他方面也准备修改容器，便会立即产生一个ConcurrentModificationException（并发修改违例）。我们将这一机制称为“立即失败”――它并不用更复杂的算法在“以后”侦测问题，而是“立即”产生违例。</p>

  <p>8.8 总结</p>

  <p>下面复习一下由标准Java（1.0和1.1）库提供的集合（BitSet未包括在这里，因为它更象一种负有特殊使命的类）：</p>

  <p>(1) 数组包含了对象的数字化索引。它容纳的是一种已知类型的对象，所以在查找一个对象时，不必对结果进行造型处理。数组可以是多维的，而且能够容纳基本数据类型。但是，一旦把它创建好以后，大小便不能变化了。</p>

  <p>(2) Vector（矢量）也包含了对象的数字索引――可将数组和Vector想象成随机访问集合。当我们加入更多的元素时，Vector能够自动改变自身的大小。但Vector只能容纳对象的句柄，所以它不可包含基本数据类型；而且将一个对象句柄从集合中取出来的时候，必须对结果进行造型处理。</p>

  <p>(3) Hashtable（散列表）属于Dictionary（字典）的一种类型，是一种将对象（而不是数字）同其他对象关联到一起的方式。散列表也支持对对象的随机访问，事实上，它的整个设计方案都在突出访问的“高速度”。</p>

  <p>(4) Stack（堆栈）是一种“后入先出”（LIFO）的队列。</p>

  <p>若你曾经熟悉数据结构，可能会疑惑为何没看到一套更大的集合。从功能的角度出发，你真的需要一套更大的集合吗？对于Hashtable，可将任何东西置入其中，并以非常快的速度检索；对于Enumeration（枚举），可遍历一个序列，并对其中的每个元素都采取一个特定的操作。那是一种功能足够强劲的工具。</p>

  <p>但Hashtable没有“顺序”的概念。Vector和数组为我们提供了一种线性顺序，但若要把一个元素插入它们任何一个的中部，一般都要付出“惨重”的代价。除此以外，队列、拆散队列、优先级队列以及树都涉及到元素的“排序”――并非仅仅将它们置入，以便以后能按线性顺序查找或移动它们。这些数据结构也非常有用，这也正是标准C++中包含了它们的原因。考虑到这个原因，只应将标准Java库的集合看作自己的一个起点。而且倘若必须使用Java 1.0或1.1，则可在需要超越它们的时候使用JGL。</p>

  <p>如果能使用Java 1.2，那么只使用新集合即可，它一般能满足我们的所有需要。注意本书在Java 1.1身上花了大量篇幅，所以书中用到的大量集合都是只能在Java1.1中用到的那些：Vector和Hashtable。就目前来看，这是一个不得以而为之的做法。但是，这样处理亦可提供与老Java代码更出色的向后兼容能力。若要用Java1.2写新代码，新的集合往往能更好地为你服务。</p>

  <p>8.9 练习</p>

  <p>(1) 新建一个名为Gerbil的类，在构建器中初始化一个int gerbilNumber（类似本章的Mouse例子）。为其写一个名为hop()的方法，用它打印出符合hop()条件的Gerbil的编号。建一个Vector，并为Vector添加一系列Gerbil对象。现在，用elementAt()方法在Vector中遍历，并为每个Gerbil都调用hop()。</p>

  <p>(2) 修改练习1，用Enumeration在调用hop()的同时遍历Vector。</p>

  <p>(3) 在AssocArray.java中，修改这个例子，令其使用一个Hashtable，而不是AssocArray。</p>

  <p>(4) 获取练习1用到的Gerbil类，改为把它置入一个Hashtable，然后将Gerbil的名称作为一个String（键）与置入表格的每个Gerbil（值）都关联起来。获得用于keys()的一个Enumeration，并用它在Hashtable里遍历，查找每个键的Gerbil，打印出键，然后将gerbil告诉给hop()。</p>

  <p>(5) 修改第7章的练习1，用一个Vector容纳Rodent（啮齿动物），并用Enumeration在Rodent序列中遍历。记住Vector只能容纳对象，所以在访问单独的Rodent时必须采用一个造型（如RTTI）。</p>

  <p>(6) 转到第7章的中间位置，找到那个GreenhouseControls.java（温室控制）例子，该例应该由三个文件构成。在Controller.java中，类EventSet仅是一个集合。修改它的代码，用一个Stack代替EventSet。当然，这时可能并不仅仅用Stack取代EventSet这样简单；也需要用一个Enumeration遍历事件集。可考虑在某些时候将集合当作Stack对待，另一些时候则当作Vector对待――这样或许能使事情变得更加简单。</p>

  <p>(7) （有一定挑战性）在与所有Java发行包配套提供的Java源码库中找出用于Vector的源码。复制这些代码，制作名为intVector的一个特殊版本，只在其中包含int数据。思考是否能为所有基本数据类型都制作Vector的一个特殊版本。接下来，考虑假如制作一个链接列表类，令其能随同所有基本数据类型使用，那么会发生什么情况。若在Java中提供了参数化类型，利用它们便可自动完成这一工作（还有其他许多好处）。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------第9章 违例差错控制</p>

  <p>Java的基本原理就是“形式错误的代码不会运行”。</p>

  <p>与C++类似，捕获错误最理想的是在编译期间，最好在试图运行程序以前。然而，并非所有错误都能在编译期间侦测到。有些问题必须在运行期间解决，让错误的缔结者通过一些手续向接收者传递一些适当的信息，使其知道该如何正确地处理遇到的问题。</p>

  <p>在C++和其他早期语言中，可通过几种手续来达到这个目的。而且它们通常是作为一种规定建立起来的，而非作为程序设计语言的一部分。典型地，我们需要返回一个值或设置一个标志（位），接收者会检查这些值或标志，判断具体发生了什么事情。然而，随着时间的流逝，终于发现这种做法会助长那些使用一个库的程序员的麻痹情绪。他们往往会这样想：“是的，错误可能会在其他人的代码中出现，但不会在我的代码中”。这样的后果便是他们一般不检查是否出现了错误（有时出错条件确实显得太愚蠢，不值得检验；注释①）。另一方面，若每次调用一个方法时都进行全面、细致的错误检查，那么代码的可读性也可能大幅度降低。由于程序员可能仍然在用这些语言维护自己的系统，所以他们应该对此有着深刻的体会：若按这种方式控制错误，那么在创建大型、健壮、易于维护的程序时，肯定会遇到不小的阻挠。</p>

  <p>①：C程序员研究一下printf()的返回值便知端详。</p>

  <p>解决的方法是在错误控制中排除所有偶然性，强制格式的正确。这种方法实际已有很长的历史，因为早在60年代便在操作系统里采用了“违例控制”手段；甚至可以追溯到BASIC语言的on error goto语句。但C++的违例控制建立在Ada的基础上，而Java又主要建立在C++的基础上（尽管它看起来更象Object Pascal）。</p>

  <p>“违例”（Exception）这个词表达的是一种“例外”情况，亦即正常情况之外的一种“异常”。在问题发生的时候，我们可能不知具体该如何解决，但肯定知道已不能不顾一切地继续下去。此时，必须坚决地停下来，并由某人、某地指出发生了什么事情，以及该采取何种对策。但为了真正解决问题，当地可能并没有足够多的信息。因此，我们需要将其移交给更级的负责人，令其作出正确的决定（类似一个命令链）。</p>

  <p>违例机制的另一项好处就是能够简化错误控制代码。我们再也不用检查一个特定的错误，然后在程序的多处地方对其进行控制。此外，也不需要在方法调用的时候检查错误（因为保证有人能捕获这里的错误）。我们只需要在一个地方处理问题：“违例控制模块”或者“违例控制器”。这样可有效减少代码量，并将那些用于描述具体操作的代码与专门纠正错误的代码分隔开。一般情况下，用于读取、写入以及调试的代码会变得更富有条理。</p>

  <p>由于违例控制是由Java编译器强行实施的，所以毋需深入学习违例控制，便可正确使用本书编写的大量例子。本章向大家介绍了用于正确控制违例所需的代码，以及在某个方法遇到</p>

  <div class="mbppagebreak"></div>
</body>
</html>
